#include "tthsum.h"
/* vim:ts=4:sw=4:noet
 * (tabspace=4)
 * 
 * Copyright (C) 2004, 2005 Walter Doekes, <walter@djcvt.net>.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "base32.h"
#include "escape.h"
#include "read.h"
#include "texts.h"
#include "thex.h"
#include "types.h"
#include "utf8.h"
#ifdef _WIN32
#	define WINDOWS_LEAN_AND_MEAN
#	include <windows.h>
#	define PATH_MAX MAX_PATH
#endif /* _WIN32 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int tthsum_generate_root(const char* filename,
		const struct tthsum_options* opt) {
	uint64_t hash[3];
	unsigned char hash_base32[40];
	/* unicode will be at most equal sized to the multibyte charset */
	wchar_t filename_uni[PATH_MAX + 1];
	/* utf8/C-escaping will be at most 4 times the mbcs equivalent */
	unsigned char filename_buf[PATH_MAX * 4 + 1];

	if(filename != NULL && strlen(filename) > PATH_MAX) {
#ifdef USE_SETERROR2
		set_error("tthsum_generate_root", TTHSUM_FILENAME_TOO_LARGE);
#endif /* USE_SETERROR2 */
		return -1;
	}
	if(thex_tiger_root(filename, hash, opt->use_mmap,
			opt->progress_every_mib) == -1)
		return -1;
	if(uint64tobase32(hash_base32, hash, 3) == -1)
		return -1;
	if(filename != NULL) {
		/* the order of calling to_utf8 or to_ctrlesc doesn't matter,
		   as they operate on chars 0-127 and 128-255 respectively. */
#ifdef _WIN32
		/* change all \\ to / on Windows */
		char filename_tmp[PATH_MAX + 1];
		char* p;
		for(p = filename_tmp; *filename != '\0'; ++filename)
			if(*filename == '\\')
				*p++ = '/';
			else
				*p++ = *filename;
		*p = '\0';
		strtoctrlesc(filename_buf, filename_tmp);
#else /* !_WIN32 */
		strtoctrlesc(filename_buf, filename);
#endif /* !_WIN32 */
		if(mbstowcs(filename_uni, filename_buf, PATH_MAX + 1) == -1) {
			if(!opt->has_locale)
				fprintf(stderr, "tthsum: warning: Locale settings are C/POSIX. "
						"See the tthsum manpage for help.\n");
#ifdef USE_SETERROR2
			set_error("mbstowcs", -1);
#endif /* USE_SETERROR2 */
			return -1;
		}
		if(wcstoutf8(filename_buf, filename_uni, PATH_MAX * 4 + 1) == -1)
			return -1;
	} else {
		strcpy(filename_buf, "-");
	}

	/* print our root hash with utf8/escaped filename */
	printf("%s  %s\n", hash_base32, filename_buf);
	return 0;
}

int tthsum_generate_roots(const char* filenames[], int files,
		const struct tthsum_options* opt) {
	int i;
	for(i = 0; i < files; ++i) {
		if(tthsum_generate_root(filenames[i], opt) == -1)
			fprintf(stderr, "tthsum: `%s': %s\n",
					filenames[i] ? filenames[i] : "-", get_error());
	}
	return 0;
}

static int tthsum_check_digest_line(unsigned char* line,
		unsigned line_len, const struct tthsum_options* opt) {
	/* 39char base32, 2 spaces, PATH_MAX utf8/C-esc file, 1 terminating null */
	wchar_t filename_uni[PATH_MAX * 4 + 1];
	unsigned char filename_mbs[PATH_MAX * 4 + 1];
	unsigned char filename[PATH_MAX * 4 + 1];
	uint64_t hash_line[3] = {0ull, 0ull, 0ull};
	uint64_t hash_file[3];
	unsigned char* linep = line;
	unsigned char tmp;
	/* must be at least 39 base32 + 2 spaces + 1 filename long */
	if(line_len < 42 || line_len > (PATH_MAX * 4 + 39 + 2)) {
#ifdef USE_SETERROR2
		set_error("tthsum_check_digest_line", TTHSUM_LINE_CORRUPT);
#endif /* USE_SETERROR2 */
		return -1;
	}
	/* read base32 hash */
	if(base32touint64(hash_line, linep, 3) == -1)
		return -1;
	/* check the two spaces */
	linep += 39;
	if(*linep != ' ' || *++linep != ' ') {
#ifdef USE_SETERROR2
		set_error("tthsum_check_digest_line", TTHSUM_LINE_CORRUPT);
#endif /* USE_SETERROR2 */
		return -1;
	}
	++linep;
	/* fetch filename and convert to local codepage.
	   unfortunately our windows utf8 routine doesn't return success unless
	   it has found a terminating null, sigh */
	tmp = *(line + line_len);
	*(line + line_len) = '\0';
	if(utf8towcs(filename_uni, linep, PATH_MAX * 4 + 1) == -1) {
		*(line + line_len) = tmp;
		return -1;
	}
	*(line + line_len) = tmp; /* yes, uncorrupting the crap */
	filename_uni[PATH_MAX * 4] = '\0'; /* make sure we're terminated */
	/* proceed */
	if(wcstombs(filename_mbs, filename_uni, PATH_MAX * 4 + 1) == (size_t)-1) {
#ifdef USE_SETERROR2
		set_error("wcstombs", -1);
#endif /* USE_SETERROR2 */
		return -1;
	}
	filename_mbs[PATH_MAX * 4] = '\0'; /* make sure we're terminated */
	if(ctrlesctostr(filename, filename_mbs) == -1)
		return -1;
	/* w00t, got filename. go check TTH on it */
	if(thex_tiger_root(filename, hash_file, opt->use_mmap,
			opt->progress_every_mib) == -1) {
		if(opt->verbose)
			fprintf(stderr, "%-14s FAILED (%s)\n", filename, get_error());
		else
			fprintf(stderr, "tthsum: `%s': %s\n", filename, get_error());
		return 0;
	}
	/* compare tth's */
	if(memcmp(hash_line, hash_file, 3 * 8) == 0) {
		if(opt->verbose)
			printf("%-14s OK\n", filename);
		return 1;
	}
	/* not equal, print an error */
	if(!opt->verbose)
		fprintf(stderr, "tthsum: %s `%s'\n", get_text(TTHSUM_MISMATCHED_TTH),
				filename);
	else
		printf("%-14s FAILED\n", filename);
	return 0;
}
	
int tthsum_check_digest(const char* filename,
		const struct tthsum_options* opt) {
	char* file_buf;
	unsigned char* file_buf_end;
	int filesize = readall_file(filename, &file_buf);
	unsigned char* s;
	unsigned char* e;
	unsigned line_no = 1;
	unsigned lines_correct = 0;
	unsigned lines_incorrect = 0;
	unsigned files_correct = 0;
	
	if(filename == NULL)
		filename = "-";
	if(filesize == -1) {
		fprintf(stderr, "tthsum: `%s': %s\n", filename, get_error());
		return 1;
	}
	/* operate on a line by line basis.. we would like to get the most out
	   of corrupt or un-decodable files (esp. if the decoding fails for some
	   files only) */
	file_buf_end = (unsigned char*)file_buf + filesize;
	s = e = file_buf;
	while(1) {
		int ret;
		/* find \r or \n, or really, any ctrl-character will do, as they're all
		   escaped */
		while(e != file_buf_end && *e >= 0x20)
			++e;
		/* test the line */
		ret = tthsum_check_digest_line(s, e - s, opt);
		if(ret == 1) {
			++lines_correct;
			++files_correct;
		} else if(ret == 0) {
			++lines_correct;
		} else {
			if(opt->warn)
				fprintf(stderr, "tthsum: `%s': %u: %s\n", filename, line_no,
						get_error());
			++lines_incorrect;
		}
		/* find the only true line separator, the \n */
		s = e;
		while(s != file_buf_end && *s != '\n')
			++s;
		if(s == file_buf_end || s + 1 == file_buf_end) /* expect \n at eof */
			break;
		/* start on the next line */
		e = ++s;
		++line_no;
	}
	/* free our readfile data */
	free(file_buf);

	if(files_correct == 0 && lines_correct == 0) {
		fprintf(stderr, "tthsum: `%s': No files checked\n", filename);
		return -1;
	} else if(lines_correct != files_correct) {
		if(opt->verbose)
			fprintf(stderr, "tthsum: %u of %u file(s) failed TTH check\n",
					lines_correct - files_correct, files_correct);
		return -1;
	}
	return 0;
}
